use anyhow::{bail, Result};
use indicatif::ProgressBar;
use quick_xml::de::from_str;
use reqwest::Client;
use serde::Deserialize;
use std::{fs::remove_file, path::Path};

use crate::{
    default::REPO_METADATA_FILE_NAME, error::DownloadError, hash::HashKind,
    utils::download_single_file,
};

#[derive(Deserialize, Default, Debug)]
pub struct Metalink {
    files: Files,
}
#[derive(Deserialize, Default, Debug)]
pub struct Files {
    file: FileItem,
}
#[derive(Deserialize, Default, Debug)]
struct FileItem {
    #[serde(rename = "@name")]
    name: String,
    timestamp: u64,
    size: u64,
    verification: Verification,
    resources: Resources,
}
#[derive(Deserialize, Default, Debug)]
struct Verification {
    #[serde(rename = "$value")]
    hashs: Vec<Hash>,
}
#[derive(Deserialize, Default, Debug)]
struct Hash {
    #[serde(rename = "@type")]
    r#type: String,
    #[serde(rename = "$value")]
    hash: String,
}
#[derive(Deserialize, Default, Debug)]
struct Resources {
    #[serde(rename = "@maxconnections")]
    max_conn: Option<usize>,
    #[serde(rename = "$value")]
    url: Vec<Url>,
}
#[derive(Deserialize, Default, Debug)]
struct Url {
    #[serde(rename = "@protocol")]
    protocol: String,
    #[serde(rename = "@type")]
    r#type: Option<String>,
    #[serde(rename = "@location")]
    location: Option<String>,
    #[serde(rename = "@preference")]
    preference: usize,
    #[serde(rename = "$value")]
    url: String,
}
impl Metalink {
    pub async fn download_repomd<P: AsRef<Path>>(
        buf: &str,
        client: &Client,
        file_path: P,
        repo_name: &str,
        pb: &ProgressBar,
    ) -> Result<String> {
        let metalink = match from_str::<Metalink>(buf) {
            Ok(s) => s,
            Err(e) => {
                bail!("Failed to parse {:?} {}", file_path.as_ref(), e)
            }
        };
        let urls = metalink.files.file.resources.url;
        let hashs = metalink.files.file.verification.hashs;
        let mut urls = urls
            .iter()
            .filter(|x| x.protocol == "https" || x.protocol == "http")
            .collect::<Vec<_>>();
        urls.sort_by(|a, b| b.preference.cmp(&a.preference));
        let _msg = repo_name.to_owned() + "-" + REPO_METADATA_FILE_NAME;
        // dbg!(msg);
        // println!("{}", msg.as_str());
        for url in urls {
            // dbg!(repo_name);
            // match download_single_file_with_pb(client, &url.url, file_path.as_ref(), &msg, pb).await
            // {
            //     Err(DownloadError::Network(e)) => {
            //         println!("{}", e);
            //         println!("try next site");
            //         remove_file(file_path.as_ref())?;
            //     }
            //     Err(download_error) => {
            //         return Err(download_error.into());
            //     }
            //     Ok(_) => {
            //         for hash in &hashs {
            //             if HashKind::from(hash.r#type.as_str())
            //                 .checksum(file_path.as_ref(), &hash.hash)?
            //             {
            //                 return Ok(url.url.clone());
            //             };
            //         }
            //     }
            // };
            match download_single_file(client, &url.url, file_path.as_ref()).await {
                Err(DownloadError::Network(e)) => {
                    println!("{}", e);
                    println!("try next site");
                    remove_file(file_path.as_ref())?;
                }
                Err(download_error) => {
                    return Err(download_error.into());
                }
                Ok(_) => {
                    for hash in &hashs {
                        if HashKind::from(hash.r#type.as_str())
                            .checksum(file_path.as_ref(), &hash.hash)?
                        {
                            return Ok(url.url.clone());
                        };
                    }
                }
            };
        }
        bail!("The contents in the metalink file are incorrect");
    }
}
#[cfg(test)]
mod tests {
    use std::{fs::File, io::Read};

    use crate::conf::metalink::Metalink;
    use console::Term;
    use indicatif::{ProgressBar, ProgressStyle};
    use reqwest::ClientBuilder;
    #[tokio::test]
    async fn test_metalink() {
        let mut f = File::open("assest/metalink").unwrap();
        // let mut buf=Vec::new();
        let pb = ProgressBar::new(0);
        let (_, width) = Term::stdout().size();

        let style = format!(
            "{{msg:{}}}{{spinner:.green}}[{{bar:{}.cyan/blue}}]{{bytes}}/{{total_bytes}} ({{bytes_per_sec}},{{eta}})",
            width / 3,
            width / 3
        );
        let style = ProgressStyle::with_template(style.as_str())
            .unwrap()
            .progress_chars("#>-");
        pb.set_style(style);
        let mut buf = String::new();
        f.read_to_string(&mut buf).unwrap();
        let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
        Metalink::download_repomd(buf.as_str(), &client, "assest/repomd.xml", "TestRepo", &pb)
            .await
            .unwrap();
        // let k = from_str::<Metalink>(buf.as_str()).unwrap();
        // dbg!(k);
    }
}
